/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.servicemix.audit.jcr;

import java.io.StringReader;
import java.util.GregorianCalendar;
import java.util.StringTokenizer;

import javax.jbi.JBIException;
import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.InOnly;
import javax.jbi.messaging.InOut;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.NormalizedMessage;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.ObservationManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jackrabbit.value.DateValue;
import org.apache.servicemix.audit.async.AbstractJmsExchangeListener;
import org.apache.servicemix.audit.async.DefaultMessageExchangeSerializer;
import org.apache.servicemix.audit.async.ExchangeSerializer;
import org.apache.servicemix.audit.async.NoBodyMessageExchangeSerializer;
import org.apache.servicemix.jbi.api.ClientFactory;
import org.apache.servicemix.jbi.api.ServiceMixClient;
import org.springmodules.jcr.SessionFactory;

/**
 * 
 * JCR auditor
 * 
 * @author vkrejcirik
 * 
 */
public class JcrAuditor extends AbstractJmsExchangeListener<MessageExchange> {

    private static final Log LOG = LogFactory.getLog(JcrAuditor.class);

    // private Repository repository;
    private JcrAuditorStrategy strategy;
    // private JcrTemplate template;
    private OwnEventListener configEventListener = new OwnEventListener();

    private SessionFactory sessionFactory;

    /**
     * 
     * Get JCR session
     * 
     * @return JcrSession
     * @throws LoginException
     * @throws RepositoryException
     */
    protected Session getSession() throws LoginException, RepositoryException {
        return sessionFactory.getSession();
    }

    @Override
    protected ExchangeSerializer<MessageExchange> getSerializer(String namespace) {

        ExchangeSerializer<MessageExchange> serializer = null;

        try {
            Session session = getSession();
            String ser = null;

            Node config = session.getRootNode().getNode(AuditContstants.CONFIG_TYPE + "/" + SlingUtils.getNodeCorrectName(namespace));

            try {

                ser = config.getProperty("serializer").getValue().getString();
            } catch (RepositoryException e) {

                config.setProperty("serializer", "DefaultSerializer");
                ser = "DefaultSerializer";
            }

            if (ser.equals("DefaultSerializer"))
                serializer = new DefaultMessageExchangeSerializer();
            else if (ser.equals("NoBodySerializer"))
                serializer = new NoBodyMessageExchangeSerializer();

            session.save();

        } catch (RepositoryException e) {
            e.printStackTrace();
        }

        if (serializer == null) {
            LOG.warn(String.format("Unable to determine serializer for namespace %s -- falling back to default", namespace));
            return new DefaultMessageExchangeSerializer();
        }

        return serializer;
    }

    @Override
    public void onAccepted(MessageExchange accepted) {

    }

    @Override
    public synchronized void onSent(MessageExchange sent) {

        try {
            if (sent.getStatus() != ExchangeStatus.ACTIVE) {
                Session session = getSession();
                strategy.processExchange(sent, session);
                LOG.debug("Exchange processed, saving session");
                session.save();
                LOG.debug("Session saved");
            }
        } catch (Exception e) {
            LOG.error("Unable to store information about message exchange " + sent.getExchangeId(), e);
            return;
        }

        LOG.info("Successfully stored information about message exchange " + sent.getExchangeId() + " in the JCR repository");
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        try {

            ObservationManager observationManager = getSession().getWorkspace().getObservationManager();
            observationManager.addEventListener(configEventListener, Event.PROPERTY_ADDED, "/content", true, null, null, false);

        } catch (UnsupportedRepositoryOperationException e) {
            throw new JBIException("Unsupported repository operation exception, unable to add Event Listener", e);
        } catch (RepositoryException e1) {
            throw new JBIException("Repository exception, unable to add Event Listener", e1);
        }

        super.afterPropertiesSet();
    }

    public String getDescription() {
        return "ServiceMix JCR Auditor";
    }

    // just some setters and getters
    /**
     * Configure the JCR Repository to connect to
     * 
     * @param repository
     */
    /*
     * public void setRepository(Repository repository) { this.repository = repository; }
     */

    /**
     * Configure the {@link JcrAuditorStrategy} to use
     * 
     * @param strategy
     */
    public void setStrategy(JcrAuditorStrategy strategy) {
        this.strategy = strategy;
    }

    /**
     * Configure the {@link JcrAuditorStrategy} to use
     * 
     * @param template
     */
    /*
     * public void setTemplate(JcrTemplate template) { this.template = template; }
     */

    @Override
    protected void createConfigNodeForNamespace(MessageExchange exchange) {

        Node configNode;

        try {
            Session session = getSession();
            configNode = RepositoryUtils.getOrCreate(session.getRootNode(), AuditContstants.CONFIG_TYPE);

            // add endpoint to config node, if not exists yet
            try {
                configNode.getNode(SlingUtils.getNodeCorrectName(exchange.getService().getNamespaceURI()));
            } catch (PathNotFoundException e) {

                Node newNode = configNode.addNode(SlingUtils.getNodeCorrectName(exchange.getService().getNamespaceURI()));

                newNode.setProperty("created", new DateValue(new GregorianCalendar()));
                newNode.setProperty("endpointId", exchange.getService().getNamespaceURI());
                newNode.setProperty("serializer", "DefaultSerializer");
                newNode.setProperty(AuditContstants.RESOURCE_TYPE, AuditContstants.CONFIG_ENDPOINT_RESOURCE_TYPE);
                session.save();
            }

        } catch (PathNotFoundException e1) {
            e1.printStackTrace();
        } catch (LoginException e1) {
            e1.printStackTrace();
        } catch (RepositoryException e1) {
            e1.printStackTrace();
        }
    }

    public class OwnEventListener implements EventListener {

        public void onEvent(EventIterator iterator) {

            LOG.info("event");

            // for every endpoints setup-node set new value of serializer
            Node node = null;
            String ser = null;

            try {
                Session session = getSession();

                while (iterator.hasNext()) {
                    try {
                        Event event = iterator.nextEvent();

                        // configure node
                        if (event.getPath().endsWith("config/serializer")) {

                            node = session.getRootNode().getNode(AuditContstants.CONFIG_TYPE);

                            try {
                                ser = node.getProperty("serializer").getValue().getString();
                            } catch (RepositoryException e) {

                                node.setProperty("serializer", "DefaultSerializer");
                                ser = "DefaultSerializer";
                            }

                            NodeIterator iter = node.getNodes();
                            Node child = null;

                            while (iter.hasNext()) {
                                child = (Node) iter.next();
                                child.setProperty("serializer", ser);
                            }
                        } else if (SlingUtils.isMessageExchangeInQueue(event.getPath())) {

                            node = session.getRootNode().getNode(AuditContstants.RESEND_TYPE);

                            try {

                                ClientFactory factory;
                                ServiceMixClient client = null;

                                factory = (ClientFactory) new InitialContext().lookup(ClientFactory.DEFAULT_JNDI_NAME);
                                client = factory.createClient();

                                String mep = node.getProperty("mep").getValue().getString();
                                String content = node.getProperty("newContent").getValue().getString();
                                // String endpoint = node.getProperty("endpoint").getValue().getString();
                                String senderEndpoint = node.getProperty("org.apache.servicemix.senderEndpoint").getValue().getString();
                                String corrId = node.getProperty("org.apache.servicemix.correlationId").getValue().getString();
                                
                                StringTokenizer tokenizer = new StringTokenizer(senderEndpoint, "{");
                                String temp = tokenizer.nextToken();

                                tokenizer = new StringTokenizer(temp, "}");
                                String ns = tokenizer.nextToken();
                                temp = tokenizer.nextToken();

                                tokenizer = new StringTokenizer(temp, ":");
                                String name = tokenizer.nextToken();

                                NormalizedMessage message = null;

                                if (mep.endsWith("in-out")) {
                                    InOut exchange = client.createInOutExchange();
                                    message = exchange.getInMessage();
                                    message.setContent(new StreamSource(new StringReader(content)));
                                    exchange.setService(new QName(ns, name));
                                    exchange.setProperty("org.apache.servicemix.senderEndpoint", senderEndpoint);
                                    exchange.setProperty("org.apache.servicemix.correlationId", corrId);

                                    /*PropertyIterator it = node.getProperties();
                                    
                                    while (it.hasNext()) {

                                        Property temp1 = (Property) it.next();
                                        String name1 = temp1.getName();
                                        String value1 = temp1.getValue().getString();

                                        if (temp1.getName().startsWith("org.apache.servicemix") || temp1.getName().startsWith("javax.jbi")) {
                                            exchange.setProperty(temp1.getName(), temp1.getValue());
                                        }
                                    }*/

                                    client.sendSync(exchange);
                                    client.done(exchange);

                                } else {
                                    InOnly exchange = client.createInOnlyExchange();
                                    message = exchange.getInMessage();
                                    message.setContent(new StreamSource(new StringReader(content)));
                                    exchange.setService(new QName(ns, name));
                                    exchange.setProperty("org.apache.servicemix.senderEndpoint", senderEndpoint);
                                    exchange.setProperty("org.apache.servicemix.correlationId", corrId);

                                    /*PropertyIterator it = node.getProperties();
                                    
                                    while (it.hasNext()) {

                                        Property temp1 = (Property) it.next();
                                        String name1 = temp1.getName();
                                        String value1 = temp1.getValue().getString();

                                        if (temp1.getName().startsWith("org.apache.servicemix") || temp1.getName().startsWith("javax.jbi")) {
                                            exchange.setProperty(temp1.getName(), temp1.getValue());
                                        }
                                    }*/

                                    client.sendSync(exchange);
                                }

                                client.close();

                            } catch (MessagingException e) {
                                e.printStackTrace();
                            } catch (NamingException e) {
                                e.printStackTrace();
                            } catch (JBIException e) {
                                e.printStackTrace();
                            }

                            node.remove();
                        }

                    } catch (RepositoryException e) {
                        e.printStackTrace();
                    }

                    if (session.hasPendingChanges()) {
                        session.save();
                    }
                    // session.save();
                }
            } catch (LoginException e1) {
                e1.printStackTrace();
            } catch (RepositoryException e) {
                e.printStackTrace();
            }
        }
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /*
     * public static void main(String[] args) {
     * 
     * ClientRepositoryFactory factory = new ClientRepositoryFactory(); try { JcrAuditor a = new JcrAuditor(); Repository repository =
     * factory.getRepository("rmi://localhost:1099/jackrabbit"); Session session = repository.login(new SimpleCredentials("admin",
     * "admin".toCharArray()));
     * 
     * session.getWorkspace().getObservationManager().addEventListener(a.configEventListener, Event.PROPERTY_ADDED, "/content", true, null,
     * null, false);
     * 
     * session.getWorkspace().getObservationManager().addEventListener(new EventListener() {
     * 
     * public void onEvent(EventIterator iterator) { System.out.println(iterator); while (iterator.hasNext()) { try { Event event =
     * iterator.nextEvent(); System.out.println(printEvent(event.getType()) + event.getPath()); } catch (RepositoryException e) {
     * e.printStackTrace(); } } }
     * 
     * }, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/content", true, null, null, true);
     * 
     * session.getWorkspace().getObservationManager().addEventListener(new EventListener() {
     * 
     * public void onEvent(EventIterator iterator) { System.out.println(iterator); while (iterator.hasNext()) { try { Event event =
     * iterator.nextEvent(); System.out.println(printEvent(event.getType()) + event.getPath()); } catch (RepositoryException e) {
     * e.printStackTrace(); } } }
     * 
     * }, Event.NODE_ADDED | Event.NODE_REMOVED, "/content", true, null, null, true);
     * 
     * } catch (Exception e) { e.printStackTrace(); } }
     */

    public static String printEvent(int type) {
        switch (type) {
        case Event.NODE_ADDED:
            return "Added ";
        case Event.NODE_REMOVED:
            return "Removed ";
        case Event.PROPERTY_ADDED:
            return "Added property ";
        case Event.PROPERTY_CHANGED:
            return "Changed property ";
        case Event.PROPERTY_REMOVED:
            return "Removed property ";
        default:
            return "????? ";
        }
    }

}
